/***********************************************************************
    filename:   CEGUIRenderedString.cpp
    created:    25/05/2009
    author:     Paul Turner
 *************************************************************************/

#include "hgeGuiRenderedString.h"
#include "hgeGuiRenderedStringComponent.h"
#include "hgeGeometryBuffer.h"
#include "hgeExceptions.h"

//----------------------------------------------------------------------------//
RenderedString::RenderedString()
{
    // set up initial line info
    appendLineBreak();
}

//----------------------------------------------------------------------------//
RenderedString::~RenderedString()
{
    clearComponents();
}

//----------------------------------------------------------------------------//
void RenderedString::appendComponent(const RenderedStringComponent& component)
{
    d_components.push_back(component.clone());
    ++d_lines.back().second;
}

//----------------------------------------------------------------------------//
void RenderedString::clearComponents()
{
    clearComponentList(d_components);
    d_lines.clear();
}

//----------------------------------------------------------------------------//
size_t RenderedString::getComponentCount() const
{
    return d_components.size();
}

//----------------------------------------------------------------------------//
RenderedString::RenderedString(const RenderedString& other)
{
    cloneComponentList(other.d_components);
    d_lines = other.d_lines;
}

//----------------------------------------------------------------------------//
RenderedString& RenderedString::operator=(const RenderedString& rhs)
{
    cloneComponentList(rhs.d_components);
    d_lines = rhs.d_lines;
    return *this;
}

//----------------------------------------------------------------------------//
void RenderedString::cloneComponentList(const ComponentList& list)
{
    clearComponentList(d_components);

    for (size_t i = 0; i < list.size(); ++i)
        d_components.push_back(list[i]->clone());    
}

//----------------------------------------------------------------------------//
void RenderedString::clearComponentList(ComponentList& list)
{
    for (size_t i = 0; i < list.size(); ++i)
        delete list[i];

    list.clear();
}

//----------------------------------------------------------------------------//
void RenderedString::split(const size_t line, float split_point,
                           RenderedString& left)
{
    // FIXME: This function is big and nasty; it breaks all the rules for a
    // 'good' function and desperately needs some refactoring work done to it.
    // On the plus side, it does seem to work though ;)

    if (line >= getLineCount())
        throw InvalidRequestException("RenderedString::split: "
            "line number specified is invalid.");

    left.clearComponents();

    if (d_components.empty())
        return;

    // move all components in lines prior to the line being split to the left
    if (line > 0)
    {
        // calculate size of range
        const size_t sz = d_lines[line - 1].first + d_lines[line - 1].second;
        // range start
        ComponentList::iterator cb = d_components.begin();
        // range end (exclusive)
        ComponentList::iterator ce = cb + sz;
        // copy components to left side
        left.d_components.assign(cb, ce);
        // erase components from this side.
        d_components.erase(cb, ce);

        LineList::iterator lb = d_lines.begin();
        LineList::iterator le = lb + line;
        // copy lines to left side
        left.d_lines.assign(lb, le);
        // erase lines from this side
        d_lines.erase(lb, le);
    }

    // find the component where the requested split point lies.
    float partial_extent = 0;

    size_t idx = 0;
    const size_t last_component = d_lines[0].second;
    for (; idx < last_component; ++idx)
    {
        partial_extent += d_components[idx]->getPixelSize().d_x;

        if (split_point <= partial_extent)
            break;
    }

    // case where split point is past the end
    if (idx >= last_component)
    {
        // transfer this line's components to the 'left' string.
        //
        // calculate size of range
        const size_t sz = d_lines[0].second;
        // range start
        ComponentList::iterator cb = d_components.begin();
        // range end (exclusive)
        ComponentList::iterator ce = cb + sz;
        // copy components to left side
        left.d_components.insert(left.d_components.end(), cb, ce);
        // erase components from this side.
        d_components.erase(cb, ce);

        // copy line info to left side
        left.d_lines.push_back(d_lines[0]);
        // erase line from this side
        d_lines.erase(d_lines.begin());

        // fix up lines in this object
        for (size_t comp = 0, i = 0; i < d_lines.size(); ++i)
        {
            d_lines[i].first = comp;
            comp += d_lines[i].second;
        }

        return;
    }

    left.appendLineBreak();
    const size_t left_line = left.getLineCount() - 1;
    // Everything up to 'idx' is xfered to 'left'
    for (size_t i = 0; i < idx; ++i)
    {
        left.d_components.push_back(d_components[0]);
        d_components.erase(d_components.begin());
        ++left.d_lines[left_line].second;
        --d_lines[0].second;
    }

    // now to split item 'idx' putting half in left and leaving half in this.
    RenderedStringComponent* c = d_components[0];
    if (c->canSplit())
    {
        RenderedStringComponent* lc = 
            c->split(split_point - (partial_extent - c->getPixelSize().d_x),
                     idx == 0);

        if (lc)
        {
            left.d_components.push_back(lc);
            ++left.d_lines[left_line].second;
        }
    }
    // can't split, if component width is >= split_point xfer the whole
    // component to it's own line in the left part (FIX #306)
    else if (c->getPixelSize().d_x >= split_point)
    {
        left.appendLineBreak();
        left.d_components.push_back(d_components[0]);
        d_components.erase(d_components.begin());
        ++left.d_lines[left_line + 1].second;
        --d_lines[0].second;
    }

    // fix up lines in this object
    for (size_t comp = 0, i = 0; i < d_lines.size(); ++i)
    {
        d_lines[i].first = comp;
        comp += d_lines[i].second;
    }
}

//----------------------------------------------------------------------------//
void RenderedString::appendLineBreak()
{
    const size_t first_component = d_lines.empty() ? 0 :
        d_lines.back().first + d_lines.back().second;

    d_lines.push_back(LineInfo(first_component, 0));
}

//----------------------------------------------------------------------------//
size_t RenderedString::getLineCount() const
{
    return d_lines.size();
}

//----------------------------------------------------------------------------//
Size RenderedString::getPixelSize(const size_t line) const
{
    if (line >= getLineCount())
        throw InvalidRequestException("RenderedString::getPixelSize: "
            "line number specified is invalid.");

    Size sz(0, 0);

    const size_t end_component = d_lines[line].first + d_lines[line].second;
    for (size_t i = d_lines[line].first; i < end_component; ++i)
    {
        const Size comp_sz(d_components[i]->getPixelSize());
        sz.d_x += comp_sz.d_x;

        if (comp_sz.d_y > sz.d_y)
            sz.d_y = comp_sz.d_y;
    }

    return sz;
}

//----------------------------------------------------------------------------//
size_t RenderedString::getSpaceCount(const size_t line) const
{
    if (line >= getLineCount())
        throw InvalidRequestException("RenderedString::getSpaceCount: "
            "line number specified is invalid.");

    size_t space_count = 0;

    const size_t end_component = d_lines[line].first + d_lines[line].second;
    for (size_t i = d_lines[line].first; i < end_component; ++i)
        space_count += d_components[i]->getSpaceCount();

    return space_count;
}

//----------------------------------------------------------------------------//
void RenderedString::draw(const size_t line, GeometryBuffer& buffer, const Vector2& position,
					  const ColourRect* mod_colours, const hgeRect* clip_rect,
					  const float space_extra) const
{
    if (line >= getLineCount())
        throw InvalidRequestException("RenderedString::draw: "
            "line number specified is invalid.");

    const float render_height = getPixelSize(line).d_y;

    Vector2 comp_pos(position);

    const size_t end_component = d_lines[line].first + d_lines[line].second;
    for (size_t i = d_lines[line].first; i < end_component; ++i)
    {
        d_components[i]->draw(buffer, comp_pos, mod_colours, clip_rect,
                              render_height, space_extra);
        comp_pos.d_x += d_components[i]->getPixelSize().d_x;
    }
}

//----------------------------------------------------------------------------//
FormattedRenderedString::FormattedRenderedString(const RenderedString& string) :
d_renderedString(&string)
{
}

//----------------------------------------------------------------------------//
FormattedRenderedString::~FormattedRenderedString()
{
}

//----------------------------------------------------------------------------//
void FormattedRenderedString::setRenderedString(const RenderedString& string)
{
	d_renderedString = &string;
}

//----------------------------------------------------------------------------//
const RenderedString& FormattedRenderedString::getRenderedString() const
{
	return *d_renderedString;
}

//----------------------------------------------------------------------------//
LeftAlignedRenderedString::LeftAlignedRenderedString(
	const RenderedString& string) :
FormattedRenderedString(string)
{
}

//----------------------------------------------------------------------------//
void LeftAlignedRenderedString::format(const Size& /*area_size*/)
{
}

//----------------------------------------------------------------------------//
void LeftAlignedRenderedString::draw(GeometryBuffer& buffer,
									 const Vector2& position,
									 const ColourRect* mod_colours,
									 const hgeRect* clip_rect) const
{
	Vector2 draw_pos(position);

	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
	{
		d_renderedString->draw(i, buffer, draw_pos, mod_colours, clip_rect, 0.0f);
		draw_pos.d_y += d_renderedString->getPixelSize(i).d_y;
	}
}

//----------------------------------------------------------------------------//
size_t LeftAlignedRenderedString::getFormattedLineCount() const
{
	return d_renderedString->getLineCount();
}

//----------------------------------------------------------------------------//
float LeftAlignedRenderedString::getHorizontalExtent() const
{
	float w = 0.0f;
	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
	{
		const float this_width = d_renderedString->getPixelSize(i).d_x;
		if (this_width > w)
			w = this_width;
	}

	return w;
}

//----------------------------------------------------------------------------//
float LeftAlignedRenderedString::getVerticalExtent() const
{
	float h = 0.0f;
	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
		h += d_renderedString->getPixelSize(i).d_y;

	return h;
}

//----------------------------------------------------------------------------//
CentredRenderedString::CentredRenderedString(const RenderedString& string) :
FormattedRenderedString(string)
{
}

//----------------------------------------------------------------------------//
void CentredRenderedString::format(const Size& area_size)
{
	d_offsets.clear();

	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
		d_offsets.push_back(
		(area_size.d_x - d_renderedString->getPixelSize(i).d_x) / 2.0f);
}

//----------------------------------------------------------------------------//
void CentredRenderedString::draw(GeometryBuffer& buffer,
								 const Vector2& position,
								 const ColourRect* mod_colours,
								 const hgeRect* clip_rect) const
{
	Vector2 draw_pos;
	draw_pos.d_y = position.d_y;

	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
	{
		draw_pos.d_x = position.d_x + d_offsets[i];
		d_renderedString->draw(i, buffer, draw_pos, mod_colours, clip_rect, 0.0f);
		draw_pos.d_y += d_renderedString->getPixelSize(i).d_y;
	}
}

//----------------------------------------------------------------------------//
size_t CentredRenderedString::getFormattedLineCount() const
{
	return d_renderedString->getLineCount();
}

//----------------------------------------------------------------------------//
float CentredRenderedString::getHorizontalExtent() const
{
	float w = 0.0f;
	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
	{
		const float this_width = d_renderedString->getPixelSize(i).d_x;
		if (this_width > w)
			w = this_width;
	}

	return w;
}

//----------------------------------------------------------------------------//
float CentredRenderedString::getVerticalExtent() const
{
	float h = 0.0f;
	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
		h += d_renderedString->getPixelSize(i).d_y;

	return h;
}

//----------------------------------------------------------------------------//
RightAlignedRenderedString::RightAlignedRenderedString(
	const RenderedString& string) :
FormattedRenderedString(string)
{
}

//----------------------------------------------------------------------------//
void RightAlignedRenderedString::format(const Size& area_size)
{
	d_offsets.clear();

	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
		d_offsets.push_back(
		area_size.d_x - d_renderedString->getPixelSize(i).d_x);
}

//----------------------------------------------------------------------------//
void RightAlignedRenderedString::draw(GeometryBuffer& buffer,
									  const Vector2& position,
									  const ColourRect* mod_colours,
									  const hgeRect* clip_rect) const
{
	Vector2 draw_pos;
	draw_pos.d_y = position.d_y;

	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
	{
		draw_pos.d_x = position.d_x + d_offsets[i];
		d_renderedString->draw(i, buffer, draw_pos, mod_colours, clip_rect, 0.0f);
		draw_pos.d_y += d_renderedString->getPixelSize(i).d_y;
	}
}

//----------------------------------------------------------------------------//
size_t RightAlignedRenderedString::getFormattedLineCount() const
{
	return d_renderedString->getLineCount();
}

//----------------------------------------------------------------------------//
float RightAlignedRenderedString::getHorizontalExtent() const
{
	float w = 0.0f;
	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
	{
		const float this_width = d_renderedString->getPixelSize(i).d_x;
		if (this_width > w)
			w = this_width;
	}

	return w;
}

//----------------------------------------------------------------------------//
float RightAlignedRenderedString::getVerticalExtent() const
{
	float h = 0.0f;
	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
		h += d_renderedString->getPixelSize(i).d_y;

	return h;
}


//----------------------------------------------------------------------------//
JustifiedRenderedString::JustifiedRenderedString(const RenderedString& string) :
FormattedRenderedString(string)
{
}

//----------------------------------------------------------------------------//
void JustifiedRenderedString::format(const Size& area_size)
{
	d_spaceExtras.clear();

	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
	{
		const size_t space_count = d_renderedString->getSpaceCount(i);
		const float string_width = d_renderedString->getPixelSize(i).d_x;

		if ((space_count == 0) || (string_width >= area_size.d_x))
			d_spaceExtras.push_back(0.0f);
		else
			d_spaceExtras.push_back(
			(area_size.d_x - string_width) / space_count);
	}
}

//----------------------------------------------------------------------------//
void JustifiedRenderedString::draw(GeometryBuffer& buffer,
								   const Vector2& position,
								   const ColourRect* mod_colours,
								   const hgeRect* clip_rect) const
{
	Vector2 draw_pos(position);

	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
	{
		d_renderedString->draw(i, buffer, draw_pos, mod_colours, clip_rect,
			d_spaceExtras[i]);
		draw_pos.d_y += d_renderedString->getPixelSize(i).d_y;
	}
}

//----------------------------------------------------------------------------//
size_t JustifiedRenderedString::getFormattedLineCount() const
{
	return d_renderedString->getLineCount();
}

//----------------------------------------------------------------------------//
float JustifiedRenderedString::getHorizontalExtent() const
{
	float w = 0.0f;
	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
	{
		const float this_width = d_renderedString->getPixelSize(i).d_x +
			d_renderedString->getSpaceCount(i) * d_spaceExtras[i];

		if (this_width > w)
			w = this_width;
	}

	return w;
}

//----------------------------------------------------------------------------//
float JustifiedRenderedString::getVerticalExtent() const
{
	float h = 0.0f;
	for (size_t i = 0; i < d_renderedString->getLineCount(); ++i)
		h += d_renderedString->getPixelSize(i).d_y;

	return h;
}

//----------------------------------------------------------------------------//
template <typename T>
RenderedStringWordWrapper<T>::RenderedStringWordWrapper(
	const RenderedString& string) :
FormattedRenderedString(string)
{
}

//----------------------------------------------------------------------------//
template <typename T>
RenderedStringWordWrapper<T>::~RenderedStringWordWrapper()
{
	deleteFormatters();
}

//----------------------------------------------------------------------------//
template <typename T>
void RenderedStringWordWrapper<T>::format(const Size& area_size)
{
	deleteFormatters();

	RenderedString rstring, lstring;
	rstring = *d_renderedString;
	float rs_width;

	T* frs;

	for (size_t line = 0; line < rstring.getLineCount(); ++line)
	{
		while ((rs_width = rstring.getPixelSize(line).d_width) > 0)
		{
			// skip line if no wrapping occurs
			if (rs_width <= area_size.d_width)
				break;

			// split rstring at width into lstring and remaining rstring
			rstring.split(line, area_size.d_width, lstring);
			frs = new T(*new RenderedString(lstring));
			frs->format(area_size);
			d_lines.push_back(frs);
			line = 0;
		}
	}

	// last line.
	frs = new T(*new RenderedString(rstring));
	frs->format(area_size);
	d_lines.push_back(frs);
}

//----------------------------------------------------------------------------//
template <typename T>
void RenderedStringWordWrapper<T>::draw(GeometryBuffer& buffer,
										const Vector2& position,
										const ColourRect* mod_colours,
										const hgeRect* clip_rect) const
{
	Vector2 line_pos(position);
	typename LineList::const_iterator i = d_lines.begin();
	for (; i != d_lines.end(); ++i)
	{
		(*i)->draw(buffer, line_pos, mod_colours, clip_rect);
		line_pos.d_y += (*i)->getVerticalExtent();
	}
}

//----------------------------------------------------------------------------//
template <typename T>
size_t RenderedStringWordWrapper<T>::getFormattedLineCount() const
{
	return d_lines.size();
}

//----------------------------------------------------------------------------//
template <typename T>
float RenderedStringWordWrapper<T>::getHorizontalExtent() const
{
	// TODO: Cache at format time.

	float w = 0;
	typename LineList::const_iterator i = d_lines.begin();
	for (; i != d_lines.end(); ++i)
	{
		const float cur_width = (*i)->getHorizontalExtent();
		if (cur_width > w)
			w = cur_width;
	}

	return w;
}

//----------------------------------------------------------------------------//
template <typename T>
float RenderedStringWordWrapper<T>::getVerticalExtent() const
{
	// TODO: Cache at format time.

	float h = 0;
	typename LineList::const_iterator i = d_lines.begin();
	for (; i != d_lines.end(); ++i)
		h += (*i)->getVerticalExtent();

	return h;
}

//----------------------------------------------------------------------------//
template <typename T>
void RenderedStringWordWrapper<T>::deleteFormatters()
{
	for (size_t i = 0; i < d_lines.size(); ++i)
	{
		// get the rendered string back from rthe formatter
		const RenderedString* rs = &d_lines[i]->getRenderedString();
		// delete the formatter
		delete d_lines[i];
		// delete the rendered string.
		delete rs;
	}

	d_lines.clear();
}
//----------------------------------------------------------------------------//
template <>
void RenderedStringWordWrapper<JustifiedRenderedString>::format(const Size& area_size)
{
	deleteFormatters();

	RenderedString rstring, lstring;
	rstring = *d_renderedString;
	float rs_width;

	FormattedRenderedString* frs;

	for (size_t line = 0; line < rstring.getLineCount(); ++line)
	{
		while ((rs_width = rstring.getPixelSize(line).d_x) > 0)
		{
			// skip line if no wrapping occurs
			if (rs_width <= area_size.d_x)
				break;

			// split rstring at width into lstring and remaining rstring
			rstring.split(line, area_size.d_x, lstring);
			frs = new JustifiedRenderedString(*new RenderedString(lstring));
			frs->format(area_size);
			d_lines.push_back(frs);
			line = 0;
		}
	}

	// last line (which we do not justify)
	frs = new LeftAlignedRenderedString(*new RenderedString(rstring));
	frs->format(area_size);
	d_lines.push_back(frs);
}